package com.godenwater.recv.server.yanyu;

import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.AttributeKey;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.CumulativeProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import com.godenwater.yanyu.IMessageBody;
import com.godenwater.yanyu.IMessageHeader;
import com.godenwater.yanyu.Symbol;
import com.godenwater.yanyu.YYMessage;
import com.godenwater.yanyu.YYMessageHeader;
import com.godenwater.yanyu.body.UpBody;
import com.godenwater.yanyu.utils.ByteUtil;

/**
 * 报文解码类，作为上行报文的解码类，此类必须实现对所有消息体的封闭细节
 * 
 * @ClassName: YfServerDataDecoder
 * @Description: 处理断包和粘包的实现类
 * @author lipujun
 * @date Mar 2, 2013
 * 
 */
public class ServerDataDecoder extends CumulativeProtocolDecoder {
	private static final AttributeKey BUF_BYTE = new AttributeKey(
			ServerDataDecoder.class, "BUF_KEY");

	private final AttributeKey CONTEXT = new AttributeKey(getClass(), "context");

	private Logger logger = LoggerFactory.getLogger(this.getClass());

	public Context getContext(IoSession session) {
		Context ctx = (Context) session.getAttribute(CONTEXT);
		if (ctx == null) {
			ctx = new Context();
			session.setAttribute(CONTEXT, ctx);
		}
		return ctx;
	}

	private class Context {
		// 状态变量
		private final IoBuffer innerBuffer;

		public Context() {
			innerBuffer = IoBuffer.allocate(100).setAutoExpand(true);
		}
	}

	/**
	 * 这个办法的返回值是重点：
	 * 
	 * 1、当内容正好时，返回false，告诉父类可以进行下一批消息的处理
	 * 
	 * 2、内容不符时，需要下一批消息的内容，此时返回false
	 * ，如许父类会将内容放进IoSession中，等下次数据来后就主动拼装再交给本类的doDecode
	 * 
	 * 3、当内容多时，返回true，因为需要再将本批数据进行读取，父类会将残剩的数据再次推送本类的doDecode
	 */
	@Override
	protected boolean doDecode(IoSession session, IoBuffer buffer,
			ProtocolDecoderOutput out) throws Exception {
		// TODO Auto-generated method stub
		logger.info(">> HEX : "
				+ StringUtils.trimAllWhitespace(buffer.getHexDump()));

		// logger.info(">> HEX : " +
		// ByteUtil.toHexString(buffer.buf().array()));

		int size = 0;
		int rcrlen = 0;
		int bodySizeLen = 0;

		if (buffer.remaining() > 0) {// 表示缓冲区中有数据
			// System.out.println("------buffer.remaining() ---------"
			// + buffer.remaining() );

			buffer.mark();// 标记当前位置，以便reset

			byte[] mode = new byte[1];
			buffer.get(mode);

			IMessageHeader header;
			if (mode[0] == (byte) Symbol.SOH) {
				// 燕禹协议
				header = new YYMessageHeader();

				// 7E 目的地址 源地址 特征 长度 时间1《数据》 ETX CRC16H CRC16L
				header.setStartBit(new byte[] { Symbol.SOH });

				// 上行的，中心站地址在前
				byte[] centerAddr = new byte[header.getCenterAddrLen()];
				buffer.get(centerAddr);
				header.setCenterAddr(centerAddr);

				// 源地址,测站地址
				byte[] stationAddr = new byte[header.getStationAddrLen()];
				buffer.get(stationAddr);
				header.setStationAddr(stationAddr);

				// 特征码
				byte[] funccode = new byte[header.getFuncCodeLen()];
				buffer.get(funccode);
				header.setFuncCode(funccode);

				// 报文长度
				byte[] bodySize = new byte[header.getBodySizeLen()];
				buffer.get(bodySize);
				header.setBodySize(bodySize);

				// 转换报文中的上下行标识及报文长度
				bodySizeLen = ByteUtil.bytesToUbyte(bodySize);// 需要考虑拆分字节;

			} else if (mode[0] == (byte) 0x24) {
				// 在此处再添加一个对卫星数据进行过程的地方
				// 24545853432C642C3133343431352C37382C
				logger.info(">> 接收北斗卫星消息 ");

				// 需要跳过18个字节
				byte[] BD = new byte[17];
				buffer.get(BD);

				// System.out.println(">> 接收北斗卫星消息    " +
				// ByteUtil.toHexString(BD));

				// -------------------------------------
				// 以上为卫星的操作
				// -------------------------------------
				header = new YYMessageHeader();

				// 上行的，中心站地址在前
				byte[] centerAddr = new byte[header.getCenterAddrLen()];
				buffer.get(centerAddr);
				header.setCenterAddr(centerAddr);

				// 源地址,测站地址
				byte[] stationAddr = new byte[header.getStationAddrLen()];
				buffer.get(stationAddr);
				header.setStationAddr(stationAddr);

				// 特征码
				byte[] funccode = new byte[header.getFuncCodeLen()];
				buffer.get(funccode);
				header.setFuncCode(funccode);

				// 报文长度
				byte[] bodySize = new byte[header.getBodySizeLen()];
				buffer.get(bodySize);
				header.setBodySize(bodySize);

				// 转换报文中的上下行标识及报文长度
				bodySizeLen = ByteUtil.bytesToUbyte(bodySize);// 需要考虑拆分字节;

			} else {

				// 处理DTU设备上线时的登录信息，发的是DTU编号和手机号
				// buffer.get(buffer.array().length);
				System.out.println(">> 非协议报文 ");
				byte[] temp = new byte[buffer.remaining()];
				buffer.get(temp);
				return true;
			}

			size = bodySizeLen;

			// 若是消息内容的长度不敷则直接返回true
			if (size > buffer.remaining()) {// 若是消息大小与缓冲区中的内容大小不匹配，则重置，相当于不读取size
				// System.out.println("------size > buffer.remaining() ---------"
				// + size + "  " + buffer.remaining());
				buffer.reset();
				return false;// 接管新数据，以拼凑成完全数据
			} else {
				// System.out
				// .println("------size < buffer.remaining() ---------BodySizeLen "
				// + BodySizeLen
				// + " size "
				// + size
				// + "  "
				// + buffer.remaining());
				byte[] bodyContent = new byte[bodySizeLen-3];// 根据前面header的内容来获取长度
				buffer.get(bodyContent);

				// 终止符
				byte[] eof = new byte[1];
				buffer.get(eof);

				// 校验码
				byte[] crc = new byte[2];
				buffer.get(crc);
				
				// 组装消息
				YYMessage message = new YYMessage();
				message.setHeader(header);
				// 根据功能码，构建body体
				IMessageBody body = new UpBody();
				body.setContents(bodyContent);
				message.setBody(body);
				message.setEOF(eof[0]);
				message.setCRC(crc);

				out.write(message);

				if (mode[0] == (byte) 0x24) {
					// buffer.reset();
					// buffer.clear();
					// 提取后续的字节，不做处理，直接当垃圾
					byte[] remain = new byte[buffer.remaining()];
					buffer.get(remain);
					logger.info("+++++++++++++++++++++++++++++++++++++++++++++++++++++++ 【"
							+ remain.length
							+ " _ "
							+ buffer.remaining()
							+ " _ " + size + "】");
					return false;
				}

				if (buffer.remaining() > 0) {// 若是读取内容后还粘了包，就让父类再给解析一次，返回true进行下一次解析
					return true;
				}
			}
		}
		return false;// 处理重发成功，让父类进行接管下个包
	}

 
	public void dispose(IoSession session) throws Exception {
		// TODO Auto-generated method stub

	}

	public void finishDecode(IoSession session, ProtocolDecoderOutput out)
			throws Exception {
		// TODO Auto-generated method stub

	}

}
